#! /usr/bin/env node
import inquirer from 'inquirer';
import chalk from 'chalk'
import path, {dirname} from 'path'
import fs from "fs-extra";
import {fileURLToPath} from 'url';
import json5 from 'json5';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

async function readFileAsLines(filePath) {
    let content = await fs.readFile(filePath)
    return content.toString().split(/\r?\n/g);  // 获取每一行
}

async function addAtAliasInViteConfig(viteConfigPath, atPath) {
    /**
     * 获取vite.config.ts文件中defineConfig的配置对象
     */
    await insertImportStatement(viteConfigPath, "import { fileURLToPath, URL } from 'node:url'")
    let lines = await readFileAsLines(viteConfigPath);
    let configStartLine = 0
    let hasBase = false
    let hasResolve = false
    let resolveStart = 0
    let hasAlias = false
    let aliasStart = 0
    let atLine = 0
    // -------------------------------- 扫描vite.config.ts文件，并记录相关信息 ----------------------------
    for (let [i, line] of lines.entries()) {
        line = line.trim()
        if (line.startsWith("export default defineConfig")) {
            configStartLine = i
        }
        if (line.startsWith("base")) {
            if (line.includes("'./'") || line.includes('"./"')) {
                hasBase = true
            }
        }
        if (line.startsWith("resolve") && !line.includes("resolves")) {
            hasResolve = true
            resolveStart = i
        }
        if (i > resolveStart && line.startsWith("alias:")) {
            hasAlias = true
            aliasStart = i
        }
        if (i > aliasStart && (line.startsWith("'@'") || line.startsWith('"@"'))) {
            atLine = i
        }
    }

    // 根据记录的vite.config.ts相关信息处理文件
    if (atLine > 0) { // 如果存在指定'@'别名的定义行，则修改@的路径
        lines[atLine] = `'@': fileURLToPath(new URL('./${atPath}', import.meta.url)),`
    } else {
        // 否则，在表示不存在@符号的别名定义行

        // 如果存在alias属性行，则只是alias中没有'@'别名定义
        if (aliasStart > 0) {
            lines = [...lines.slice(0, aliasStart + 1), `'@': fileURLToPath(new URL('./${atPath}', import.meta.url)),`, ...lines.slice(aliasStart + 1, lines.length)]
        } else {
            // 不存在alias属性行，则进一步判断是否有resolve字段
            if (resolveStart > 0) { // 存在resolve字段，但是resolve里面没有alias属性
                if (/resolve\s*:\s*{\s*}\s*,?$/.test(lines[resolveStart])) { // 说明该行是单行的resolve，例如resolve: {}
                    lines[resolveStart] = `    resolve: {
        alias: {
            '@': fileURLToPath(new URL('./${atPath}', import.meta.url)),
        }
    }`
                } else { // 否则改行是多行的resolve的开始行，例如resolve: {，后续还有resolve的行
                    lines = [...lines.slice(0, resolveStart + 1), `alias: {
            '@': fileURLToPath(new URL('./${atPath}', import.meta.url)),
        }`, ...lines.slice(resolveStart + 1, lines.length)]
                }
            } else {
                // 不存在resolve属性行
                lines = [...lines.slice(0, configStartLine + 1), `    resolve: {
        alias: {
            '@': fileURLToPath(new URL('./${atPath}', import.meta.url)),
        }
    },`, ...lines.slice(configStartLine + 1, lines.length)]
            }
        }
    }

    await fs.writeFile(viteConfigPath, lines.join('\r\n'))
}

async function addAtAliasInTsConfig(tsConfigPath, atPath) {
    /**
     * 在tsconfig.json文件中添加‘@'符号别名
     */
    let _ = await fs.readFile(tsConfigPath) // 以json5方式加载json文件，因为tsconfig.json中可能添加注释，不属于标准json语法
    let tsConfigObj = json5.parse(_)
    tsConfigObj.compilerOptions.baseUrl = '.'
    if (tsConfigObj.compilerOptions.hasOwnProperty('paths')) { // 如果已经有paths字段
        if (tsConfigObj.compilerOptions.paths.hasOwnProperty("@/*")) {
            tsConfigObj.compilerOptions.paths["@/*"].push(`${atPath}/*`)
        } else {
            tsConfigObj.compilerOptions.paths["@/*"] = [`${atPath}/*`]
        }
    } else {
        tsConfigObj.compilerOptions.paths = {
            "@/*": [
                `${atPath}/*`
            ]
        }
    }

    await fs.writeJson(tsConfigPath, tsConfigObj, {spaces: 2})
}

async function insertUsePlugin(filePath, useObj) {
    /**
     * 在main.ts中添加Vue的use语句
     * @param filePath: 需要添加use语句的ts文件路径，必须是ts文件
     * @param useObj: 需要添加的use语句的对象，例如，则useObj="createPinia()"或者useObj="router"，括号需要根据情况适当添加
     */
    let content = await fs.readFile(filePath)
    let lines = content.toString().split(/\r?\n/g);  // 获取每一行
    let mountLineNo = 0 // .mount()函数调用所在的行号
    for (let [index, line] of lines.entries()) {  // index从0开始
        if (line.includes(`.use(${useObj})`)) {
            return // 说明已经加载了当前插件
        }
        if (line.includes(`.mount(`)) {
            mountLineNo = index;
            break
        }
    }
    let modifyLine = lines[mountLineNo]
    modifyLine = modifyLine.replace(".mount(", `.use(${useObj}).mount(`)
    lines[mountLineNo] = modifyLine

    await fs.writeFile(filePath, lines.join("\r\n"))
}

async function insertImportStatement(filePath, statement) {
    /**
     * 在ts文件头部插入import语句
     * @param filePath: 需要添加import语句的ts文件，必须是ts文件
     * @param statement: 需要插入的import语句，如import path from 'path', import {defineConfig} from 'vite',
     */
    let content = await fs.readFile(filePath)

    // ------------------------------- 分析import语句导入的对象信息 --------------------------------
    let importObj = statement.split('from')
    let importName = importObj[0].replace("import", ''); // 导入当前模块的名称
    importName = importName.replace("{", '');
    importName = importName.replace("}", '');
    importName = importName.trim()
    let importFile = importObj[1];  // 导入语句的目标文件，从该文件中导入对象
    let importNames = importName.split(",")
    let singleName = true // 是否导入的是单个对象
    if (importNames.length > 1) {
        singleName = false;
    }
    let needImportName = [...importNames];  // 导入语句中存在，且文件中不存在的导入对象名称
    // ============================== 分析import语句导入的对象信息 ===========================

    // let lines = content.toString().split(/[\r\n]+/);  // 获取每一行
    let lines = content.toString().split(/\r?\n/g);  // 获取每一行

    let lastImportLine = 0
    for (let [index, line] of lines.entries()) {  // index从0开始
        if (line.trim().startsWith('#!')) {

        } else if (line.trim() !== "" && !line.trim().startsWith("import")) {
            // 如果line不以import开头，则当前行为import头部区域最后一行
            lastImportLine = index;
            break
        }
    }

    for (let index = 0; index < lastImportLine; index++) {
        if (lines[index].includes(importFile)) { // 如果存在从同一个文件导入的语句
            if (singleName) {  // 如果只需要导入一个对象，则检查该对象是否已在该语句中导入
                let namesInLine = lines[index].split('from')[0]
                if (namesInLine.includes(importNames[0])) {
                    // 说明当前import语句导入的对象已经导入过了
                    return
                }
            } else { // 需要导入多个对象
                for (let importName of importNames) { // 需要插入的语句中的多个对象
                    if (lines[index].includes(importName)) {  // 如果当前行包括了需要导入的对象名称
                        needImportName = needImportName.filter(item => item !== importName); // 从需要导入的名称数组中，移除存在的名称
                    }
                }
                if (needImportName.length === 0) {
                    // 需要导入的对象都已经导入了
                    return
                } else if (needImportName.length === importNames.length) {
                    // 存在多个导入对象，且当前文件中不存在任何已经导入的对象
                } else {
                    // todo 需要导入的对象有部分已经导入
                    // import { fileURLToPath, URL } from 'node:url'
                }
            }
        }
    }


    if (lastImportLine - 1 >= 0 && lines[lastImportLine - 1].trim() === "") { // 防止正式代码之前的空行
        lastImportLine = lastImportLine - 1
    }
    // 至此，当前文件中不存在需要导入的语句，则添加
    let length = lines.length;

    lines = [
        ...lines.slice(0, lastImportLine),
        statement,
        ...lines.slice(lastImportLine, length)]

    await fs.writeFile(filePath, lines.join("\r\n"))
}

async function getDirectories(sourcePath) {
    const entries = await fs.readdir(sourcePath, {withFileTypes: true});

    return Promise.all(entries.map(async (entry) => {
        if (entry.isDirectory()) {
            return entry.name;
        }
    })).then((dirs) => dirs.filter(Boolean)); // 移除可能的undefined值（如果目录为空）
}

class Creator {
    constructor(name, target) {
        this.name = name;
        this.target = target;
    }

    // 创建项目部分
    async create(projectName) {
        const templateFolder = path.resolve(__dirname, "..", "template")

        let templates = await getDirectories(templateFolder);

        let {template} = await inquirer.prompt([
            {
                name: "template",
                type: "list",
                message: "Choose a template",
                choices: templates,
                default: "vue-electron-template"
            }
        ])

        let usePinia, useRouter, atPath, useTaoBao

        if (template.toString().startsWith("pywebview")) {
            usePinia = false  // 模板里已经添加过了，无需再添加
            useRouter = false
            atPath = "no"
            useTaoBao = true
        } else {
            usePinia, useRouter, atPath, useTaoBao = await inquirer.prompt([
                {
                    name: "usePinia",
                    type: "confirm",
                    message: "Whether you need use pinia?",
                    default: true,
                },
                {
                    name: "useRouter",
                    type: "confirm",
                    message: "Whether you need use vue-router?",
                    default: true,
                },
                {
                    name: "atPath",
                    type: "input",
                    message: "配置@路径别名为什么(no表示不配置，其他表示路径名称)?",
                    default: "src"
                },
                {
                    name: "useTaoBao",
                    type: "confirm",
                    message: "是否需要配置项目npm使用淘宝源？",
                    default: true,
                }
            ])
        }


        // 将源文件夹复制到目标
        await fs.copy(path.resolve(templateFolder, template), this.target);
        let mainTsFile = path.resolve(this.target, "src", "main.ts")
        let package_json = await fs.readJson(path.resolve(this.target, "package.json"))
        package_json.name = projectName

        if (usePinia) {
            // ------------------------------------ 添加pinia依赖 -------------------------------------------------
            package_json.dependencies.pinia = "^2.1.7";

            // ---------------------------------- 修改main.ts，添加pinia引用语句 ------------------------------------
            await insertImportStatement(mainTsFile, "import {createPinia} from 'pinia'")
            await insertUsePlugin(mainTsFile, "createPinia()")
            // ================================ 修改main.ts，添加pinia引用语句 ======================================

            // -------------------------------- 添加stores文件 -----------------------------------------------
            await fs.mkdirs(path.resolve(this.target, "src", "stores"))
            await fs.writeFile(path.resolve(this.target, "src", "stores", "calculate.ts"), `import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('calculate', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
})
            `)


        }


        if (useRouter) { // 如果需要使用vue-router
            // ---------------------------------- 修改package.json -------------------------------------------
            package_json.dependencies["vue-router"] = "^4.2.5";

            // ------------------------------ 修改main.ts，添加router引用语句 -----------------------------------
            await insertImportStatement(mainTsFile, "import router from './router'")
            await insertUsePlugin(mainTsFile, "router")
            // ------------------------------ 修改main.ts，添加router引用语句 -----------------------------------

            // -------------------------------- 创建router.ts文件 ---------------------------------------------
            let routerContent = `import { createRouter, createWebHistory } from 'vue-router'
import HomeView from './components/HelloWorld.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (About.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import('@/components/HelloWorld.vue')
    }
  ]
})

export default router
`
            await fs.writeFile(path.resolve(this.target, "src", "router.ts"), routerContent)
        }

        if (useTaoBao) {
            let npm_rc = `# 配置 npm 的默认镜像源为淘宝镜像源
registry=https://registry.npmmirror.com
disturl=https://registry.npmmirror.com/-/binary/node

# 配置此项目的局部镜像源之 electron，如果证书过期，可以换一个
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
ELECTRON_BUILDER_BINARIES_MIRROR="https://npmmirror.com/mirrors/electron-builder-binaries/"

# 确保项目中的依赖包版本与 package.json 文件中的版本精确匹配
save-exact = true

# node 版本与 package.json 配置不同的时候，中断命令执行
engine-strict = true
`
            await fs.writeFile(path.resolve(this.target, ".npmrc"), npm_rc);
        }


        if (atPath !== "no") {
            // console.log("路径别名为", atPath)
            let viteConfigTsPath = path.resolve(this.target, "vite.config.ts")
            let tsConfigTsPath = path.resolve(this.target, "tsconfig.json")
            await addAtAliasInViteConfig(viteConfigTsPath, atPath)
            await addAtAliasInTsConfig(tsConfigTsPath, atPath)
        }

        await fs.writeJson(path.resolve(this.target, "package.json"), package_json, {spaces: 2});

        let gitIgnore = `# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

`
        await fs.writeFile(path.resolve(this.target, ".gitignore"), gitIgnore);
        console.log(`\r\nSuccessfully created project ${chalk.bold.cyan(this.name)}`);
        console.log(`\r\n  cd ${chalk.cyan(this.name)}`);
        console.log("  npm install");
        console.log("  npm run dev\r\n");
    }
}


// 不能使用 export default {Creator};
export default Creator;
